/**
 * Copyright (c) 2011 ~ 2015 Deepin, Inc.
 *               2013 ~ 2015 jouyouyun
 *
 * Author:      jouyouyun <jouyouwen717@gmail.com>
 * Maintainer:  jouyouyun <jouyouwen717@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 **/

package core

import (
	"github.com/BurntSushi/xgb/xproto"
	"github.com/BurntSushi/xgbutil/keybind"
	"strings"
)

const (
	shortcutDelim = "-"
)

/**
 * Format shortcut to suit xgb interface
 * input: '<Control><Super>T'
 * output: 'control-mod4-t'
 **/
func ToXGBFormat(shortcut string) string {
	if len(shortcut) == 0 {
		return ""
	}
	return wmShortcutToXGB(shortcut)
}

/**
 * Format shortcut to suit wm interface
 * input: 'control-mod4-t'
 * output: '<Control><Super>T'
 **/
func ToWMFormat(shortcut string) string {
	if len(shortcut) == 0 {
		return ""
	}

	return xgbShortcutToWM(shortcut)
}

func IsAccelEqual(s1, s2 string) bool {
	if len(s1) == 0 || len(s2) == 0 {
		return false
	}

	if s1 == s2 {
		return true
	}

	s1 = ToWMFormat(s1)
	s2 = ToWMFormat(s2)
	news1 := ToXGBFormat(s1)
	news2 := ToXGBFormat(s2)
	if news1 == news2 {
		return true
	}

	xu, err := Initialize()
	if err != nil {
		return false
	}

	mod1, keys1, _ := keybind.ParseString(xu, news1)
	mod2, keys2, _ := keybind.ParseString(xu, news2)
	if mod1 != mod2 {
		return false
	}

	return isKeycodesEqual(keys1, keys2)
}

func IsKeycodeMatch(mod uint16, keycode int, s string) bool {
	if len(s) == 0 {
		return false
	}

	xu, err := Initialize()
	if err != nil {
		return false
	}

	s = ToWMFormat(s)
	return keybind.KeyMatch(xu, ToXGBFormat(s),
		mod, xproto.Keycode(keycode))
}

func wmShortcutToXGB(shortcut string) string {
	keys := getKeysFromWMShortcut(shortcut)
	if len(keys) == 1 {
		return keys[0]
	}

	var ret string
	for i, key := range keys {
		if i != 0 {
			ret += shortcutDelim
		}
		ret += keysymToWeird(keyToXGBMod(key))
	}

	return ret
}

func xgbShortcutToWM(shortcut string) string {
	keys := getKeysFromXGBShortcut(shortcut)
	if len(keys) == 1 {
		return keys[0]
	}

	var (
		ret    string
		length = len(keys)
	)
	for i, key := range keys {
		key = strings.Title(xgbModToKey(key))
		if i == (length - 1) {
			ret += key
			continue
		}

		ret += "<" + key + ">"
	}

	return ret
}

func getKeysFromWMShortcut(shortcut string) []string {
	if len(shortcut) == 0 {
		return nil
	}

	var (
		keys  []string
		key   string
		start int
		end   int
		match bool
	)
	for i, ch := range shortcut {
		if ch == '<' {
			match = true
			start = i
			continue
		}

		if ch == '>' && match {
			end = i
			match = false

			var tmp string
			for j := start + 1; j < end; j++ {
				tmp += string(shortcut[j])
			}
			keys = append(keys, tmp)
			continue
		}

		if !match {
			key += string(shortcut[i])
		}
	}
	keys = append(keys, key)

	return uniqueXGBKeys(filterSpecialKeys(keys))
}

func getKeysFromXGBShortcut(shortcut string) []string {
	var (
		idx int = -1

		keys []string
		key  string
	)
	for i, ch := range shortcut {
		if ch == '-' {
			if (idx + 1) == i {
				idx = -1
				keys = append(keys, "-")
			} else {
				keys = append(keys, key)
				idx = i
				key = ""
			}
			continue
		}

		key += string(shortcut[i])
	}
	if len(key) > 0 {
		keys = append(keys, key)
	}

	return filterSpecialKeys(keys)
}

func uniqueXGBKeys(keys []string) []string {
	if len(keys) == 0 {
		return nil
	}

	var ret []string
	length := len(keys)
	for i, k := range keys {
		if i == length-1 {
			break
		}
		tmp := strings.ToLower(k)
		if tmp == "primary" || tmp == "control" {
			if isItemInList("control", ret) {
				continue
			}

			ret = append(ret, "control")
		} else {
			ret = append(ret, k)
		}
	}
	ret = append(ret, keys[length-1])

	return ret
}

func filterSpecialKeys(keys []string) []string {
	if len(keys) == 0 {
		return nil
	}

	var ret []string
	length := len(keys)
	for i, key := range keys {
		if i == length-1 {
			break
		}
		tmp := strings.ToLower(key)
		if tmp == "lock" || tmp == "caps_lock" ||
			tmp == "mod2" || tmp == "num_lock" {
			continue
		}

		ret = append(ret, key)
	}
	ret = append(ret, keys[length-1])

	return ret
}

/**
 * check single key valid
 **/
func isValidAccelKey(key string) bool {
	tmp := strings.ToLower(key)
	switch tmp {
	case "super_l", "super_r":
		return true
	}
	return false
}

func keyToXGBMod(key string) string {
	tmp := strings.ToLower(key)
	switch tmp {
	case "caps_lock":
		return "lock"
	case "alt":
		return "mod1"
	case "meta":
		return "mod1"
	case "num_lock":
		return "mod2"
	case "super":
		return "mod4"
	case "hyper":
		return "mod4"
	}

	return key
}

func xgbModToKey(mod string) string {
	switch mod {
	case "mod1":
		return "alt"
	case "mod2":
		return "num_lock"
	case "mod4":
		return "super"
	case "lock":
		return "caps_lock"
	}

	return mod
}

func keysymToWeird(sym string) string {
	switch sym {
	case "-":
		return "minus"
	case "=":
		return "equal"
	case "\\":
		return "backslash"
	case "?":
		return "question"
	case "!":
		return "exclam"
	case "#":
		return "numbersign"
	case ";":
		return "semicolon"
	case "'":
		return "apostrophe"
	case "<":
		return "less"
	case ".":
		return "period"
	case "/":
		return "slash"
	case "(":
		return "parenleft"
	case "[":
		return "bracketleft"
	case ")":
		return "parenright"
	case "]":
		return "bracketright"
	case "\"":
		return "quotedbl"
	case " ":
		return "space"
	case "$":
		return "dollar"
	case "+":
		return "plus"
	case "*":
		return "asterisk"
	case "_":
		return "underscore"
	case "|":
		return "bar"
	case "`":
		return "grave"
	case "@":
		return "at"
	case "%":
		return "percent"
	case ">":
		return "greater"
	case "^":
		return "asciicircum"
	case "{":
		return "braceleft"
	case ":":
		return "colon"
	case ",":
		return "comma"
	case "~":
		return "asciitilde"
	case "&":
		return "ampersand"
	case "}":
		return "braceright"
	}

	return sym
}

func isItemInList(item string, items []string) bool {
	for _, v := range items {
		if v == item {
			return true
		}
	}

	return false
}
